package org.hit.burkun.swalk;

import java.util.ArrayList;
import java.util.HashMap;

import cern.colt.function.tdouble.DoubleDoubleFunction;
import cern.colt.function.tdouble.DoubleFunction;
import cern.colt.matrix.tdouble.DoubleMatrix1D;
import cern.colt.matrix.tdouble.impl.DenseDoubleMatrix1D;
import cern.jet.math.tdouble.DoubleFunctions;



public class SimplePageRank extends LinkedListNetwork{
	
	
	
	public SimplePageRank(int n , int f, ArrayList<FeatureField> list,
		boolean useWeiFunc) {
		super(n, 0, f, list, null, null, useWeiFunc);
	}

	private void initAdj() {
		adjLinks = new ArrayList<>(list.size());
		// init arr, 从0开始
		for (int i = 0; i < dim; i++) {
			adjLinks.add(i, new HashMap<Integer, Double>());
		}

	}

	private void initQadj() {
		QAdjLinks = new ArrayList<>(list.size());
		// init arr, 从0开始
		for (int i = 0; i < dim; i++) {
			QAdjLinks.add(i, new HashMap<Integer, Double>());
		}
	}

	private void initDqAdj() {
		dQAdjLinks = new ArrayList<>(list.size());
		// init arr, 从0开始
		for (int i = 0; i < dim; i++) {
			dQAdjLinks.add(i, new HashMap<Integer, Double>());
		}
	}
	
	@Override
	public void buildAdjacencyMatrix(DoubleMatrix1D param) {
		initAdj();
		for (FeatureField ff : list) {
			int c = ff.column;
			int r = ff.row;
			// reverse
			DoubleMatrix1D f = ff.features;
			double score = f.zDotProduct(param);
			if(useWeiFunc){
				score = weightingFunction(score);
			}
			adjLinks.get(c).put(r, score);
			adjLinks.get(r).put(c, score);
		}
	}

	@Override
	public ArrayList<HashMap<Integer, Double>> buildTransitionTranspose(
			double alpha) {
		// 改过来
		initQadj(); // 一定要这么做！
		int r, c;
		for (int i = 0; i < this.dim; rowSums[i++] = 0)
			;
		for (int i = 0; i < this.list.size(); i++) {
			r = this.list.get(i).row;
			c = this.list.get(i).column;
			rowSums[r] += this.adjLinks.get(r).get(c);
			if (r != c)
				rowSums[c] += this.adjLinks.get(c).get(r);
		}

		// build the transpose of Q
		double value;
		for (int i = 0; i < this.list.size(); i++) {
			r = this.list.get(i).row;
			c = this.list.get(i).column;
			value = this.adjLinks.get(r).get(c);
			value *= (1 - alpha);

			QAdjLinks.get(c).put(r, value / rowSums[r]);

			if (r == c)
				continue;

			QAdjLinks.get(r).put(c, value / rowSums[c]);
		}
		return QAdjLinks;
	}
	//全局情况下设置alpha为0
	@Override
	public ArrayList<HashMap<Integer, Double>> transitionDerivativeTranspose(
			int featureIndex, double alpha,DoubleMatrix1D param) {
		initDqAdj();
		// SparseCCDoubleMatrix2D dQt = new SparseCCDoubleMatrix2D(this.dim,
		// this.dim);
		// System.out.println("transitionDerivativeTranspose...");
		// derivative row sums
		int r, c;
		double[] dRowSums = new double[this.dim];
		for (int i = 0; i < this.list.size(); i++) {
			r = this.list.get(i).row;
			c = this.list.get(i).column;
			double x = this.list.get(i).features.zDotProduct(param);
			double fs = list.get(i).features.get(featureIndex);
			double dw = weightingFunctionDerivative(x, fs);
			dRowSums[r] += dw;
			if (r != c)
				dRowSums[c] += dw;
		}

		double value;
		for (int i = 0; i < this.list.size(); i++) {
			r = this.list.get(i).row;
			c = this.list.get(i).column;
			double score = 0;
			if (adjLinks.get(r).containsKey(c)) {
				score = adjLinks.get(r).get(c);
			}
			double x = this.list.get(i).features.zDotProduct(param);
			double fs = list.get(i).features.get(featureIndex);
			double dw = weightingFunctionDerivative(x,fs);
			value = (dw * rowSums[r]) - (score * dRowSums[r]);
			value *= (1 - alpha);
			value /= Math.pow(rowSums[r], 2);
			dQAdjLinks.get(c).put(r, value);
			// dQt.set(c, r, value);

			if (c == r)
				continue;

			double score1 = 0;
			if (adjLinks.get(c).containsKey(r)) {
				score1 = adjLinks.get(c).get(r);
			}
			value = (dw * rowSums[c]) - (score1 * dRowSums[c]);
			value *= (1 - alpha);
			value /= Math.pow(rowSums[c], 2);
			// dQt.set(r, c, value);
			dQAdjLinks.get(r).put(c, value);
		}
		return dQAdjLinks;
	}
	
	@Override
	public void pageRankAndGradient (DoubleMatrix1D param, final double alpha, double eps) {
		buildAdjacencyMatrix(param);
		ArrayList<HashMap<Integer, Double>> Qt = buildTransitionTranspose(alpha);
		//debug
		//System.out.println("Qt is full one" + isFullOne(Qt));
		//更改，进行一次pr
		//double EPSILON = 1e-6;
		double EPSILON = eps;
		DoubleMatrix1D oldP = new DenseDoubleMatrix1D(dim);        // the value of p in the previous iteration
						
		DoubleMatrix1D oldDp = new DenseDoubleMatrix1D(dim);       // the value of dp in the previous iteration
		                                                           // ...starts with all entries 0 
		boolean [] dpConverged = new boolean [f];
		boolean allDpConverged = false;
		boolean pConverged = false;
		p.assign(1.0 / dim); 
		for (int k = 0; k < f; k++){                           // for every parameter
			dp[k].assign(DoubleFunctions.constant(0));
		}//设置初值为0
		DoubleMatrix1D tmp = new DenseDoubleMatrix1D(dim);
		ArrayList<ArrayList<HashMap<Integer, Double>>> tdt = new ArrayList<>(f);

		for (int k = 0; k < f; k++){
			//计算qt
			tdt.add(k, transitionDerivativeTranspose(k, alpha, param));
		}
		//int counter = 0;
		//计算p/w, power method
		while (!allDpConverged) {
			allDpConverged = true;
			//System.out.println(Thread.currentThread() + " do iter in power methods..." + (counter++));
			if (!pConverged) {
				oldP.assign(p);
				p = zMult(Qt, oldP);
				p.assign(new DoubleFunction(){

					@Override
					public double apply(double argument) {
						return argument * alpha;
					}
					
				});
				oldP.assign(p, new DoubleDoubleFunction() {
					@Override
					public double apply(double arg0, double arg1) {
						return Math.abs(arg0-arg1);
					}
				});
				double errorSums = oldP.zSum();
				if (errorSums < EPSILON)
					pConverged = true;
			}
			
			for (int k = 0; k < f; k++) {
				if (dpConverged[k]) continue;
				//迭代求pwk的过程
				oldDp.assign(dp[k]);
				tmp = zMult(tdt.get(k), p);
				//tdt[k].zMult(p, tmp);
				dp[k] = zMult(Qt, oldDp);
				//Qt.zMult(oldDp, dp[k]);
				dp[k].assign(tmp, DoubleFunctions.plus);
				oldDp.assign(dp[k], new DoubleDoubleFunction() {
					@Override
					public double apply(double arg0, double arg1) {
						return Math.abs(arg0-arg1);
					}
				});
				double oldDpSum = oldDp.zSum();
				//System.out.println(Thread.currentThread() + " dpr error: " + (oldDpSum));
				if (oldDpSum < EPSILON)
					dpConverged[k] = true;
				else
					allDpConverged = false;
			}
		}
	}
}
